package cn.itsub.proxy.server.work;

import java.net.BindException;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

import cn.itsub.proxy.server.work.config.ServerProperties;
import cn.itsub.proxy.server.work.handlers.UserChannelHandler;
import cn.itsub.proxy.server.work.metrics.handler.BytesMetricsHandler;
import cn.itsub.proxy.common.protocol.IdleCheckHandler;
import cn.itsub.proxy.common.protocol.MessageDecoder;
import cn.itsub.proxy.common.protocol.MessageEncoder;
import cn.itsub.proxy.server.work.config.ProxyConfig;
import cn.itsub.proxy.server.work.config.ProxyConfig.ConfigChangedListener;
import cn.itsub.proxy.server.work.handlers.ServerChannelHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import lombok.extern.slf4j.Slf4j;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;

@Slf4j
public class ProxyServerStarter implements ConfigChangedListener {

    /**
     * max packet is 1M.
     */


    private NioEventLoopGroup serverWorkerGroup;

    private NioEventLoopGroup serverBossGroup;

    private ServerBootstrap userServerBootstrap = new ServerBootstrap();

    public ProxyServerStarter() {

        serverBossGroup = new NioEventLoopGroup(8);
        serverWorkerGroup = new NioEventLoopGroup(8);
        userServerBootstrap.group(serverBossGroup, serverWorkerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addFirst(new BytesMetricsHandler());
                        ch.pipeline().addLast(new UserChannelHandler());
                    }
                });

        ProxyConfig.getInstance().addConfigChangedListener(this);
    }

    public void start() {
        //监听客户端连接
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new MessageDecoder());
                ch.pipeline().addLast(new MessageEncoder());
                ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
                ch.pipeline().addLast(new ServerChannelHandler());
            }
        });

        try {
            String bind = ServerProperties.getInstance().getBind();
            int port = ServerProperties.getInstance().getPort();
            bootstrap.bind(bind, port).get();
            log.info("proxy server start on port " + port);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        if (ServerProperties.getInstance().isSslEnable()) {
            String host = ServerProperties.getInstance().getSslBind();
            int port = ServerProperties.getInstance().getSslPort();
            initializeSSLTCPTransport(host, port, new SslContextCreator().initSSLContext());
        }



        startUserPort();

    }

    private void initializeSSLTCPTransport(String host, int port, final SSLContext sslContext) {
        ServerBootstrap b = new ServerBootstrap();
        b.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                try {
                    pipeline.addLast("ssl", createSslHandler(sslContext, ServerProperties.getInstance().isSslNeedsClientAuth()));
                    ch.pipeline().addLast(new MessageDecoder());
                    ch.pipeline().addLast(new MessageEncoder());
                    ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
                    ch.pipeline().addLast(new ServerChannelHandler());
                } catch (Throwable th) {
                    log.error("Severe error during pipeline creation", th);
                    throw th;
                }
            }
        });
        try {

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(host, port);
            f.sync();
            log.info("proxy ssl server start on port {}", port);
        } catch (InterruptedException ex) {
            log.error("An interruptedException was caught while initializing server", ex);
        }
    }


    private void startUserPort() {

        List<Integer> ports = ProxyConfig.getInstance().getUserPorts();
        for (int port : ports) {
            try {
                userServerBootstrap.bind(port).get();
                log.info("bind user port " + port);
            } catch (Exception ex) {

                // BindException表示该端口已经绑定过
                if (!(ex.getCause() instanceof BindException)) {
                    throw new RuntimeException(ex);
                }
            }
        }

    }

    public void bindWanPort(int port) {

        try {
            userServerBootstrap.bind(port).get();
            log.info("bind user port " + port);
        } catch (Exception ex) {

            // BindException表示该端口已经绑定过
            if (!(ex.getCause() instanceof BindException)) {
                throw new RuntimeException(ex);
            }
        }

    }

    @Override
    public void onChanged() {
        startUserPort();
    }


    public void stop() {
        serverBossGroup.shutdownGracefully();
        serverWorkerGroup.shutdownGracefully();
    }

    private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
        SSLEngine sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);
        if (needsClientAuth) {
            sslEngine.setNeedClientAuth(true);
        }

        return new SslHandler(sslEngine);
    }


}
